package lang
//NOTE: ARRAYS ARE CALLABLE AND YOU CALL THEM WITH THE INDEX BECAUSE I AM A LAZY FUCKTARD
import (
	"fmt"
	"strings"
	"unicode"
	"utf8"
)

type item struct {
	typ itemType
	val string
}

func (i item) String() string {
	switch {
	case i.typ == itemEOF:
		return "EOF"
	case i.typ == itemError:
		return i.val
	case i.typ > itemKeyword:
		return fmt.Sprintf("<%s>", i.val)
	case len(i.val) > 10:
		return fmt.Sprintf("%.10q...", i.val)
	}
	return fmt.Sprintf("[%s:%q]", i.typ, i.val)
}

type itemType int

const (
	itemError     itemType = iota
	itemBool               // boolean constant (true, false)
	itemEOF                // EOF
	itemIdent              // name (main, list1)
	itemLParen             // (
	itemLBrace             // {
	itemRParen             // )
	itemRBrace             // }
	itemNumber             // number literal (1, 422.32, -86)
	itemRawString          // string with no substitution ('hello world')
	itemString             // string with substitution ("hello, world")
	itemEquals             // =
	itemBINOP              // delimits operators (FIXME not really binary only(
	itemPlus               // +
	itemMinus              // -
	itemMult               // *
	itemDiv                // /
	itemLAnd               // logic and
	itemLOr                // logic or
	itemLNot               // logic not
	itemLEq                // ==, logic equals
	itemLNeq               // !=, logic not equal
	itemLGT                // >
	itemLGTE               // >=
	itemLLT                // <
	itemLLTE               // <=
	itemBinEnd             //no more operators
	itemComma              // ,
	itemDot                // .
	itemKeyword            //delimeter
	itemFunc
	itemReturn
	itemIf
	itemElse
	itemWhile //TODO for
)

//pretty printing

var itemName = map[itemType]string{
	itemError:     "error",
	itemBool:      "bool",
	itemEOF:       "EOF",
	itemIdent:     "ident",
	itemLParen:    "lparen",
	itemLBrace:    "lbrace",
	itemRParen:    "rparen",
	itemRBrace:    "rbrace",
	itemNumber:    "number",
	itemRawString: "raw",
	itemString:    "str",
	itemEquals:    "=",
	itemPlus:      "+",
	itemMinus:     "-",
	itemMult:      "*",
	itemLAnd:      "&&",
	itemLOr:       "||",
	itemLNot:      "!",
	itemLEq:       "==",
	itemLNeq:      "!=",
	itemLGT:       ">",
	itemLGTE:      ">=",
	itemLLT:       "<",
	itemLLTE:      "<=",
	itemDiv:       "/",
	itemComma:     ",",
	itemDot:       ".",
	//keyword
	itemFunc:   "func",
	itemReturn: "return",
	itemIf:     "if",
	itemElse:   "else",
	itemWhile:  "while",
}

func (i itemType) String() string {
	s := itemName[i]
	if s == "" {
		return fmt.Sprintf("item%d", int(i))
	}
	return s
}

var key = map[string]itemType{
	"func":   itemFunc,
	"return": itemReturn,
	"if":     itemIf,
	"else":   itemElse,
	"while":  itemWhile,
}

const eof = -1

type stateFn func(*lexer) stateFn

type lexer struct {
	name  string    //name of input, for error reporting
	input string    //what is being scanned
	state stateFn   //next function
	pos   int       //position in input
	start int       //start position in input
	width int       //width of last rune read
	items chan item //channel of stuff we have scanned
}

//get the next rune in the input
func (l *lexer) next() (rune int) {
	if l.pos >= len(l.input) {
		l.width = 0
		return eof
	}
	rune, l.width = utf8.DecodeRuneInString(l.input[l.pos:])
	l.pos += l.width
	return rune
}
//move back a rune
func (l *lexer) backup() {
	l.pos -= l.width
}
func (l *lexer) peek() (rune int) {
	k := l.next()
	l.backup()
	return k
}
//emit item to client
func (l *lexer) emit(t itemType) {
	l.items <- item{t, l.input[l.start:l.pos]}
	l.start = l.pos
}
//ignore all pending input
func (l *lexer) ignore() {
	l.start = l.pos
}
//consume next rune if valid
func (l *lexer) accept(valid string) bool {
	if strings.IndexRune(valid, l.next()) >= 0 {
		return true
	}
	l.backup()
	return false
}
//accept until rune is not in set
func (l *lexer) acceptRun(valid string) {
	for strings.IndexRune(valid, l.next()) >= 0 {
	}
	l.backup()
}

//get current line; not a good way to do it, but we avoid peek double-counting
func (l *lexer) lineNumber() int {
	return 1 + strings.Count(l.input[:l.pos], "\n")
}

//return an error token and return a nil state, thus terminating lexing
func (l *lexer) errorf(format string, args ...interface{}) stateFn {
	l.items <- item{itemError, fmt.Sprintf(format, args...)}
	return nil
}

func (l *lexer) nextItem() item {
	for {
		select {
		case item := <-l.items:
			return item
		default:
			l.state = l.state(l)
		}
	}
	panic("musn't get here")
}

func (l *lexer) scanNumber() bool {
	l.accept("-") //TODO we shouldn't need this
	digits := "0123456789"
	if l.accept("0") && l.accept("xX") {
		digits = "0123456789ABCDEF"
	}
	l.acceptRun(digits)
	if l.accept(".") {
		l.acceptRun(digits)
	}
	if isAlphaNumeric(l.peek()) {
		l.next()
		return false
	}
	return true
}

//create a lexer for given input string
func lex(name, input string) *lexer {
	l := &lexer{
		name:  name,
		input: input,
		state: lexRoot,
		items: make(chan item, 2), //2 is more than enough; we shouldn't even need this
	}
	return l
}

//root lexer
func lexRoot(l *lexer) stateFn {
	switch r := l.next(); {
	case r == eof:
		l.emit(itemEOF)
		return nil
	case isSpace(r):
		l.ignore()
	case r == '/' && l.peek() == '*' || l.peek() == '/': //FIXME there must be a better way
		return lexComment
	case r == '"':
		return lexStr
	case r == '\'':
		return lexRStr
	case '0' <= r && r <= '9':
		return lexNumber
	case r == '(':
		l.emit(itemLParen)
	case r == '{':
		l.emit(itemLBrace)
	case r == ')':
		l.emit(itemRParen)
	case r == '}':
		l.emit(itemRBrace)
	case r == '=' && l.peek() == '=':
		l.next()
		l.emit(itemLEq)
	case r == '=':
		l.emit(itemEquals)
	case r == '+':
		l.emit(itemPlus)
	case r == '&' && l.peek() == '&':
		l.next()
		l.emit(itemLAnd)
	case r == '|' && l.peek() == '|':
		l.next()
		l.emit(itemLOr)
	case r == '!' && l.peek() == '=':
		l.next()
		l.emit(itemLNeq)
	case r == '!':
		l.emit(itemLNot)
	case r == '>' && l.peek() == '=':
		l.next()
		l.emit(itemLGTE)
	case r == '>':
		l.emit(itemLGT)
	case r == '<' && l.peek() == '=':
		l.next()
		l.emit(itemLLTE)
	case r == '<':
		l.emit(itemLLT)
	case r == '-':
		k := l.peek()
		if '0' <= k && k <= '9' {
			return lexNumber
		}
		l.emit(itemMinus)
	case r == '*':
		l.emit(itemMult)
	case r == '/':
		l.emit(itemDiv)
	case r == ',':
		l.emit(itemComma)
	case r == '.':
		l.emit(itemDot)
	case isAlphaNumeric(r):
		l.backup()
		return lexIdent
	default:
		return l.errorf("invalid: %#U", r)
	}
	return lexRoot
}

func lexNumber(l *lexer) stateFn {
	if !l.scanNumber() {
		return l.errorf("bad number syntax: %q", l.input[l.start:l.pos])
	}
	l.emit(itemNumber)
	return lexRoot
}

func lexComment(l *lexer) stateFn {
	//is it a single line comment?
	n := l.next()
	// / = yes, * = no
	if n == '/' {
		l.ignore()
		//dump everything until a newline
		i := strings.Index(l.input[l.pos:], "\n")
		if i < 0 {
			//we hit EOF before the comment ended, so let's just handle it gracefully
			l.pos = len(l.input)
			l.ignore()
			return lexRoot //we just let lexRoot handle the EOF
		}
		//move to the newline and one past it
		l.pos += i + 1
		l.ignore()
		return lexRoot
	}
	//continue until a */
	i := strings.Index(l.input[l.pos:], "*/")
	if i < 0 {
		return l.errorf("unclosed comment")
	}
	l.pos += i + 2 //need to advance two, because */ = two characters
	l.ignore()
	return lexRoot
}

func lexIdent(l *lexer) stateFn {
Loop:
	for {
		switch r := l.next(); {
		case isAlphaNumeric(r):
			//absorb it
		default:
			l.backup() //this character wasn't part of the identifier, so don't use it
			word := l.input[l.start:l.pos]
			switch {
			case key[word] > itemKeyword: //it's a keyword
				l.emit(key[word])
			case word == "true", word == "false": //a boolean constant
				l.emit(itemBool)
			default:
				l.emit(itemIdent)
			}
			break Loop
		}
	}
	return lexRoot
}

func lexStr(l *lexer) stateFn {
Loop:
	for {
		switch l.next() {
		case '\\':
			if r := l.next(); r != eof && r != '\n' {
				break //it's okay, so absorb it
			}
			fallthrough //let the next case handle it
		case eof, '\n':
			return l.errorf("unterminated string")
		case '"':
			break Loop //reached end of string
		}
	}
	l.emit(itemString)
	return lexRoot
}

func lexRStr(l *lexer) stateFn {
Loop:
	for {
		switch l.next() {
		case eof, '\n':
			return l.errorf("unterminated raw string")
		case '\\':
			l.next()
			//just include as-is
		case '\'':
			break Loop //end of raw string
		}
	}
	l.emit(itemRawString)
	return lexRoot
}

func isSpace(r int) bool {
	switch r {
	case ' ', '\t', '\n', '\r':
		return true
	}
	return false
}

func isAlphaNumeric(r int) bool {
	return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r)
}
